<!--
    by: yuanbiao 2020年11月24日
    name: x-screenshot
    notes:
-->
<template>
  <div :class="[
    prefixCls, `${prefixCls}-${type}`, `${prefixCls}-${size}`,
    {[`${prefixCls}-saving`]: isSaving}
  ]">
    <div :class="`${prefixCls}__control`"
      @mousedown.stop="toggleHandler"
      @touchstart.stop="toggleHandler"
    >
      <slot name="toggle">
        <i class="xvu-iconfont xvu-screenshot"></i>
      </slot>
    </div>
    <canvas v-if="showCanvas" :class="`${prefixCls}__canvas`" ref="canvas"
      :style="canvasStyles" :width="screenInfo.x1" :height="screenInfo.y1"
      @mousedown.stop.prevent="onCutterDown"
      @touchstart.stop.prevent="onCutterDown"
      @mousemove="onCutterMove"
      @touchmove="onCutterMove"
      @mouseup="onCutterUp"
      @touchend="onCutterUp"
      @mouseleave="onCutterUp"
      @touchleave="onCutterUp"
    ></canvas>
    <div v-if="(showCanvas && showToolsbar) || isOperating" :class="`${prefixCls}__range`"
      :style="rangeStyles"
    >
      <div :class="`${prefixCls}__range_canvas`"></div>
      <div v-for="(v,i) in rangePositionList" :key="i"
        :class="[`${prefixCls}__range_operate`, `${prefixCls}__range_operate_${v.name}`]"
        @mousedown.stop="onOperateDown(v)"
        @touchstart.stop="onOperateDown(v)"
      ></div>
    </div>
    <div v-if="showCanvas && showToolsbar" ref="toolsbar"
      :class="`${prefixCls}__toolsbar`" :style="toolsbarStyles"
    >
      <div v-for="(v,i) in toolsbarList" :key="i" :title="v.label"
        :class="[`${prefixCls}__toolsbar_item`, {
          active: !v.immediate && (toolsbarActiveIndex === i)
        }]"
        @mousedown.stop="toolsbarItemClickHandler(v,i)"
        @touchstart.stop="toolsbarItemClickHandler(v,i)"
      >
        <DrawBoard v-if="v.name === 'pen'" ref="drawboard"
          :type="type" :size="size" :targetElement="`.${prefixCls}__range_canvas`"
          :placement="drawboardConfig.placement" :multiple="drawboardConfig.multiple"
        ><i class="xvu-iconfont xvu-pen" slot="toggle"></i></DrawBoard>
        <i v-else :class="v.icon"></i>
      </div>
    </div>
    <div v-show="loadingCount > 0" :class="`${prefixCls}__loading`"
      :style="loadingStyles"
    >
      <div :class="`${prefixCls}__loading_content`">
        <p><i class="xvu-iconfont xvu-spinner"></i></p>
        <p>正在处理，请稍后...</p>
      </div>
    </div>
  </div>
</template>

<script>
import DrawBoard from '@/components/drawboard/index.js'
import {dataUrl2File, downloadFileByA, deepCopy, debounce, loadImage} from '@/script/utils.js'
// import { snapdom } from '@zumer/snapdom';
import html2canvas from 'html2canvas'
const domtoimage = require('dom-to-image')

const prefixCls = 'x-screenshot'
export default {
  name: 'x-screenshot',
  components: {
    DrawBoard
  },
  props: {
    type: {
      type: String,
      default: 'default' // default|primary|info|success|warning|danger|brown
    },
    size: {
      type: String,
      default: 'default' // default|large|small
    },
    options: {
      type: Object,
      default () {
        return {}
      }
    },
    zIndex: {
      type: Number,
      default: 10000
    }
  },
  data() {
    return {
      prefixCls: prefixCls,
      showCanvas: false,
      showToolsbar: false,
      ctxScreen: null,
      imageData: '',
      htmlStylesBak: {},
      isCutting: false,
      isOperating: false,
      isBulbOff: false,
      isSaving: false,
      toolsbarActiveIndex: -1,
      operateActive: {},
      loadingCount: 0,
      screenInfo: {
        x0: 0, y0: 0,
        x1: window.innerWidth || document.documentElement.clientWidth,
        y1: window.innerHeight || document.documentElement.clientHeight
      },
      screenshotInfo: {
        x0: 0, y0: 0, x1: 0, y1: 0,
        width: 0, height: 0, xmin: 0, ymin: 0
      },
      drawboardConfig: {
        placement: 'top',
        multiple: 'multiple'
      },
      toolsbarInfo: {
        left: 0, top: 0, width: 0, height: 0
      },
      toolsbarList: [
        {name: 'pen', label: '画笔', icon: 'xvu-iconfont xvu-pen'},
        {name: 'bulb', label: '开灯/关灯', icon: 'xvu-iconfont xvu-bulb', immediate: true},
        {name: 'save', label: '保存', icon: 'xvu-iconfont xvu-save', immediate: true},
        {name: 'close', label: '关闭', icon: 'xvu-iconfont xvu-close', immediate: true},
      ],
      rangePositionList: [
        {name: 'lt', label: '左上', x: 'x0', y: 'y0'},
        {name: 'lm', label: '左中', x: 'x0', y: 'y_'},
        {name: 'lb', label: '左下', x: 'x0', y: 'y1'},
        {name: 'mt', label: '中上', x: 'x_', y: 'y0'},
        {name: 'mb', label: '中下', x: 'x_', y: 'y1'},
        {name: 'rt', label: '右上', x: 'x1', y: 'y0'},
        {name: 'rm', label: '右中', x: 'x1', y: 'y_'},
        {name: 'rb', label: '右下', x: 'x1', y: 'y1'}
      ]
    }
  },
  computed: {
    canvasStyles () {
      return {
        zIndex: this.zIndex
      }
    },
    rangeStyles () {
      return {
        left: `${this.screenshotInfo.xmin}px`,
        top: `${this.screenshotInfo.ymin}px`,
        width: `${this.screenshotInfo.width}px`,
        height: `${this.screenshotInfo.height}px`,
        zIndex: this.zIndex + 1
      }
    },
    toolsbarStyles () {
      return {
        left: `${this.toolsbarInfo.left}px`,
        top: `${this.toolsbarInfo.top}px`,
        zIndex: this.zIndex + 2
      }
    },
    loadingStyles () {
      return {
        zIndex: this.zIndex + 3
      }
    }
  },
  mounted () {
    window.addEventListener('resize', this.resizeHandler)
  },
  beforeDestroy () {
    window.removeEventListener('resize', this.resizeHandler)
  },
  methods: {
    // 开启/关闭截图功能
    toggleHandler: debounce(function () {
      if (!this.showCanvas) {
        this.open()
      } else {
        this.close()
      }
    }, 200),
    // 工具栏按钮事件
    toolsbarItemClickHandler: debounce(function (item, index) {
      if (item.name === 'pen' && this.$refs.drawboard[0]) {
        this.toolsbarActiveIndex = this.$refs.drawboard[0].active ? index : -1
      } else if (item.name === 'bulb') {
        this.isBulbOff = !this.isBulbOff
        this.initCanvas()
        this.drawRange()
      } else if (item.name === 'save') {
        this.save()
      } else if (item.name === 'close') {
        this.close()
      }
    }, 200),
    // 初始化canvas幕布
    initCanvas () {
      this.ctxScreen = this.$refs.canvas ? this.$refs.canvas.getContext('2d') : null
      if (!this.ctxScreen) return
      this.ctxScreen.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height)
      this.ctxScreen.beginPath()
      this.ctxScreen.fillStyle = this.isBulbOff ? '#666666' : 'rgba(0,0,0,0.3)'
      this.ctxScreen.fillRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height)
      this.ctxScreen.closePath()
    },
    // 清除矩形剪辑区域的像素
    drawRange () {
      this.ctxScreen.moveTo(this.screenshotInfo.xmin, this.screenshotInfo.ymin)
      this.ctxScreen.save()
      this.ctxScreen.beginPath()
      this.ctxScreen.rect(
        this.screenshotInfo.xmin, this.screenshotInfo.ymin,
        this.screenshotInfo.width, this.screenshotInfo.height
      )
      this.ctxScreen.closePath()
      this.ctxScreen.clip()
      this.ctxScreen.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height)
      this.ctxScreen.restore()
    },
    // 框选截图区域step1 按下
    onCutterDown (e) {
      if (e.which === 3) {
        this.close()
        return
      }
      if (this.showToolsbar) return
      this.isCutting = true
      this.showToolsbar = false
      // 记录鼠标落点
      this.$set(this.screenshotInfo, 'x0', e.clientX || e.targetTouches[0].clientX)
      this.$set(this.screenshotInfo, 'y0', e.clientY || e.targetTouches[0].clientY)
      this.$set(this.screenshotInfo, 'x1', this.screenshotInfo.x0)
      this.$set(this.screenshotInfo, 'y1', this.screenshotInfo.y0)
      this.$set(this.screenshotInfo, 'xmin', this.screenshotInfo.x0)
      this.$set(this.screenshotInfo, 'ymin', this.screenshotInfo.y0)
      this.$set(this.screenshotInfo, 'width', 0)
      this.$set(this.screenshotInfo, 'height', 0)
    },
    // 框选截图区域step2 移动
    onCutterMove (e) {
      if (!this.isCutting) return
      // 记录鼠标终点
      this.$set(this.screenshotInfo, 'x1', e.clientX || e.targetTouches[0].clientX)
      this.$set(this.screenshotInfo, 'y1',  e.clientY || e.targetTouches[0].clientY)
      let width = Math.abs(this.screenshotInfo.x1 - this.screenshotInfo.x0)
      let height = Math.abs(this.screenshotInfo.y1 - this.screenshotInfo.y0)
      let xmin = Math.min(this.screenshotInfo.x0, this.screenshotInfo.x1)
      let ymin = Math.min(this.screenshotInfo.y0, this.screenshotInfo.y1)
      this.$set(this.screenshotInfo, 'xmin', xmin)
      this.$set(this.screenshotInfo, 'ymin', ymin)
      this.$set(this.screenshotInfo, 'width', width)
      this.$set(this.screenshotInfo, 'height', height)
      this.initCanvas()
      this.drawRange()
    },
    // 框选截图区域step3 结束
    onCutterUp () {
      if (!this.isCutting) return
      this.isCutting = false
      this.checkScreenshotInfo()
      if (
        (this.screenshotInfo.x1 !== this.screenshotInfo.x0) &&
        (this.screenshotInfo.y1 !== this.screenshotInfo.y0)
      ) {
        this.showToolsbar = true
        this.$nextTick(() => {
          this.updateToolsbarInfo()
        })
      }
    },
    // 框选区域操作按钮step1 按下
    onOperateDown (item) {
      if (this.isOperating) return
      this.operateActive = deepCopy(item || {})
      this.isOperating = true
      this.showToolsbar = false
      window.addEventListener('mousemove', this.onOperateMove)
      window.addEventListener('touchmove', this.onOperateMove)
      window.addEventListener('mouseup', this.onOperateUp)
      window.addEventListener('touchend', this.onOperateUp)
      window.addEventListener('mouseleave', this.onOperateUp)
      window.addEventListener('touchleave', this.onOperateUp)
    },
    // 框选区域操作按钮step2 移动
    onOperateMove (e) {
      if (!this.isOperating) return
      // 记录鼠标终点
      this.$set(this.screenshotInfo, this.operateActive.x, e.clientX || e.targetTouches[0].clientX)
      this.$set(this.screenshotInfo, this.operateActive.y, e.clientY || e.targetTouches[0].clientY)
      let width = Math.abs(this.screenshotInfo.x1 - this.screenshotInfo.x0)
      let height = Math.abs(this.screenshotInfo.y1 - this.screenshotInfo.y0)
      let xmin = Math.min(this.screenshotInfo.x0, this.screenshotInfo.x1)
      let ymin = Math.min(this.screenshotInfo.y0, this.screenshotInfo.y1)
      this.$set(this.screenshotInfo, 'xmin', xmin)
      this.$set(this.screenshotInfo, 'ymin', ymin)
      this.$set(this.screenshotInfo, 'width', width)
      this.$set(this.screenshotInfo, 'height', height)
      this.initCanvas()
      this.drawRange()
      this.$refs.drawboard && this.$refs.drawboard[0] && this.$refs.drawboard[0].resize()
    },
    // 框选区域操作按钮step3 结束
    onOperateUp () {
      if (!this.isOperating) return
      this.isOperating = false
      window.removeEventListener('mousemove', this.onOperateMove)
      window.removeEventListener('touchmove', this.onOperateMove)
      window.removeEventListener('mouseup', this.onOperateUp)
      window.removeEventListener('touchend', this.onOperateUp)
      window.removeEventListener('mouseleave', this.onOperateUp)
      window.removeEventListener('touchleave', this.onOperateUp)
      this.checkScreenshotInfo()
      if (
        (this.screenshotInfo.x1 !== this.screenshotInfo.x0) &&
        (this.screenshotInfo.y1 !== this.screenshotInfo.y0)
      ) {
        this.showToolsbar = true
        this.$nextTick(() => {
          this.updateToolsbarInfo()
          this.$refs.drawboard[0] && this.$refs.drawboard[0].resize()
        })
      }
    },
    // 更新工具条的位置信息
    updateToolsbarInfo () {
      if (!this.$refs.toolsbar) return
      let toolsbarInfo = this.$refs.toolsbar.getBoundingClientRect()
      this.$set(this.toolsbarInfo, 'width', toolsbarInfo.width)
      this.$set(this.toolsbarInfo, 'height', toolsbarInfo.height)
      // 设置画笔弹窗显示的位置/变量声明
      let placementList = ['top', 'bottom', 'left', 'right']
      let blackList = []
      let whiteList = []
      // 垂直方向
      let top = 0
      let ymax = Math.max(this.screenshotInfo.y0, this.screenshotInfo.y1)
      let ymin = Math.min(this.screenshotInfo.y0, this.screenshotInfo.y1)
      if (this.screenInfo.y1 - ymax >= (toolsbarInfo.height + 20)) {
        top = 10 + ymax
        blackList.push('top')
      } else if (ymin - this.screenInfo.y0 >= (toolsbarInfo.height + 20)) {
        top = ymin - toolsbarInfo.height - 10
        blackList.push('bottom')
      } else {
        top = ymax - toolsbarInfo.height - 10
        blackList.push('bottom')
      }
      // 水平方向
      let left = 0
      let xmax = Math.max(this.screenshotInfo.x0, this.screenshotInfo.x1)
      let xmin = Math.min(this.screenshotInfo.x0, this.screenshotInfo.x1)
      let x_center = (xmin + xmax) / 2
      let x_left = x_center - toolsbarInfo.width / 2
      let x_right = x_center + toolsbarInfo.width / 2
      if ((x_left >= 0) && (x_right <= this.screenInfo.x1)) {
        left = x_left
      } else if (x_left < 0) {
        left = 0
      } else {
        left = xmax - toolsbarInfo.width
      }
      this.$set(this.toolsbarInfo, 'left', left)
      this.$set(this.toolsbarInfo, 'top', top)
      // 设置画笔弹窗显示的位置/计算
      if (x_center > (this.screenInfo.x1 / 2)) blackList.push('right')
      else blackList.push('left')
      if (top > (this.screenInfo.y1 / 2)) blackList.push('bottom')
      else blackList.push('top')
      whiteList = placementList.filter(v => !blackList.includes(v))
      this.$set(this.drawboardConfig, 'placement', whiteList[0])
    },
    // 矫正截屏数据
    checkScreenshotInfo () {
      let xmin = Math.min(this.screenshotInfo.x0, this.screenshotInfo.x1)
      let ymin = Math.min(this.screenshotInfo.y0, this.screenshotInfo.y1)
      let xmax = Math.max(this.screenshotInfo.x0, this.screenshotInfo.x1)
      let ymax = Math.max(this.screenshotInfo.y0, this.screenshotInfo.y1)
      this.$set(this.screenshotInfo, 'x0', xmin)
      this.$set(this.screenshotInfo, 'y0', ymin)
      this.$set(this.screenshotInfo, 'x1', xmax)
      this.$set(this.screenshotInfo, 'y1', ymax)
    },
    // 打开截屏功能
    open () {
      let htmlNode = document.documentElement || document.querySelector('html')
      let htmlInfo = getComputedStyle(htmlNode)
      this.htmlStylesBak = htmlInfo
      htmlNode.style.overflow = 'hidden'
      this.showCanvas = true
      this.$nextTick(() => {
        this.initCanvas()
      })
      document.addEventListener('keyup', this.hotkeyClose)
    },
    // 关闭截屏功能
    close () {
      let htmlNode = document.documentElement || document.querySelector('html')
      htmlNode.style.overflow = this.htmlStylesBak.overflow
      this.showCanvas = false
      this.showToolsbar = false
      this.isBulbOff = false
      this.isCutting = false
      this.toolsbarActiveIndex = -1
      document.removeEventListener('keyup', this.hotkeyClose)
    },
    hotkeyClose (e) {
      let keyCode = e.keyCode || e.which || e.charCode
      if (!this.showCanvas || keyCode !== 27) return
      this.close()
    },
    // 保存截屏为图片
    save () {
      if (this.options.dependent === 'domtoimage') {
        this.saveByDomtoimage()
      // } else if (this.options.dependent === 'htmlcanvas') {
      //   this.saveByHtml2canvas()
      // } else if (this.options.dependent === 'snapdom') {
      //   this.saveBySnapdom()
      } else {
        this.saveByHtml2canvas()
      }
    },
    // saveBySnapdom () {
    //   let el = document.body || document.querySelector('body')
    //   this.isSaving = true
    //   this.loadingCount = 0
    //   this.$nextTick (async() => {
    //     let result = await snapdom(el, { scale: 2, embedFonts: true})
    //     this.full2area(result.url).then((url) => {
    //       let downloadFile = this.options.downloadFile || this.downloadFileByBase64
    //       downloadFile(url)
    //     })
    //     this.isSaving = false
    //   })
    // },
    saveByHtml2canvas () {
      // html2canvas方案，不支持transform等css3特性
      let htmlNode = document.documentElement || document.querySelector('html')
      this.isSaving = true
      this.$nextTick (() => {
        this.loadingCount++
        html2canvas(htmlNode, {
          x: this.screenshotInfo.xmin,
          y: this.screenshotInfo.ymin,
          width: this.screenshotInfo.width,
          height: this.screenshotInfo.height,
          allowTaint: true
        }).then((canvas) => {
          let base64Str = canvas.toDataURL('image/jpeg')
          let downloadFile = this.options.downloadFile || this.downloadFileByBase64
          downloadFile(base64Str)
        }).catch((err) => {
          console.log('保存截屏失败：', err)
        }).finally(() => {
          this.loadingCount--
          this.isSaving = false
        })
      })
    },
    saveByDomtoimage () {
      // domtoimage方案
      let htmlNode = document.documentElement || document.querySelector('html')
      this.isSaving = true
      this.$nextTick (() => {
        this.loadingCount++
        domtoimage.toPng(htmlNode, {
          bgcolor: '#ffffff',
          filter: (node) => {
            return ![...(node.classList || [])].includes('x-screenshot__loading')
          }
        }).then((dataUrl) => {
          this.full2area(dataUrl).then((url) => {
            let downloadFile = this.options.downloadFile || this.downloadFileByBase64
            downloadFile(url)
          })
        }).catch((err) => {
          console.log('保存截屏失败：', err)
        }).finally(() => {
          this.loadingCount--
          this.isSaving = false
        })
      })
    },
    full2area(fullSrc) {
      if (!fullSrc) return Promise.reject('没有可用的图片')
      let htmlNode = document.documentElement || document.querySelector('html')
      // canvas1
      let cvs1 = document.createElement('canvas')
      cvs1.width = htmlNode.clientWidth
      cvs1.height = htmlNode.clientHeight
      cvs1.style = `position:fixed;top:-${cvs1.height}px;left:-${cvs1.width}px;width:${cvs1.width}px;height:${cvs1.height}px`
      document.body.appendChild(cvs1)
      // canvas2
      let cvs2 = document.createElement('canvas')
      cvs2.width = this.screenshotInfo.width
      cvs2.height = this.screenshotInfo.height
      cvs2.style = `position:fixed;top:-${cvs2.height}px;left:-${cvs2.width}px;width:${cvs2.width}px;height:${cvs2.height}px`
      document.body.appendChild(cvs2)
      // img裁剪&&下载
      return loadImage(fullSrc).then((imgObj) => {
        let ctx1 = cvs1.getContext('2d')
        let ctx2 = cvs2.getContext('2d')
        ctx1.drawImage(imgObj, 0, 0, cvs1.width, cvs1.height)
        let imgData1 = ctx1.getImageData(
          this.screenshotInfo.xmin, this.screenshotInfo.ymin,
          this.screenshotInfo.width, this.screenshotInfo.height
        )
        ctx2.putImageData(imgData1, 0, 0)
        let base64Str = cvs2.toDataURL('image/jpeg')
        return base64Str
      })
    },
    downloadFileByBase64 (base64Str) {
      let url = base64Str
      try {
        url = window.URL.createObjectURL(dataUrl2File(base64Str))
      } catch (e) {
        url = decodeURIComponent(base64Str)
        console.log('downloadFileByBase64:', e)
      }
      downloadFileByA({from: url, to: '截图.png'})
    },
    resizeHandler () {
      this.$set(this.screenInfo, 'x1', window.innerWidth || document.documentElement.clientWidth)
      this.$set(this.screenInfo, 'y1', window.innerHeight || document.documentElement.clientHeight)
      this.showToolsbar && this.updateToolsbarInfo()
      if (this.$refs.canvas) {
        this.$nextTick(() => {
          this.initCanvas()
          this.drawRange()
        })
      }
    }
  }
}
</script>
